package com.kis;

/**
 * Created by wongloong on 16-5-13.
 */

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: test
 * Date: 12-5-24
 * Time: 下午5:37
 * To change this template use File | Settings | File Templates.
 */
public class ConsistencyHash {
    private TreeMap<Long, Object> nodes = null;
    //真实服务器节点信息
    private List<Object> shards = new ArrayList();
    //设置虚拟节点数目
    private int VIRTUAL_NUM = 4;

    /**
     * 初始化一致环
     */
    public void init() {
        shards.add("192.168.0.102:28080/user/userService");
        shards.add("192.168.0.102:28081/user/userService");
        shards.add("192.168.0.102:28082/user/userService");
        shards.add("192.168.0.102:28083/user/userService");
        shards.add("192.168.0.102:28084/user/userService");

        nodes = new TreeMap<Long, Object>();
        for (int i = 0; i < shards.size(); i++) {
            Object shardInfo = shards.get(i);
            for (int j = 0; j < VIRTUAL_NUM; j++) {
//                nodes.put(hash(computeMd5("SHARD-" + i + "-NODE-" + j), j), shardInfo);
            }
        }
        printMap();
        System.out.println("aaaa");
    }

    /**
     * 根据key的hash值取得服务器节点信息
     *
     * @param hash
     * @return
     */
    public Object getShardInfo(long hash) {
        Long key = hash;
        SortedMap<Long, Object> tailMap = nodes.tailMap(key);
        if (tailMap.isEmpty()) {
            key = nodes.firstKey();
        } else {
            key = tailMap.firstKey();
        }
        return nodes.get(key);
    }

    /**
     * 打印圆环节点数据
     */
    public void printMap() {
        System.out.println(nodes);
    }

    /**
     * 根据2^32把节点分布到圆环上面。
     *
     * @param digest
     * @param nTime
     * @return
     */
    public long hash(String key) {
        ByteBuffer buf = ByteBuffer.wrap(key.getBytes());
        int seed = 0x1234ABCD;

        ByteOrder byteOrder = buf.order();
        buf.order(ByteOrder.LITTLE_ENDIAN);

        long m = 0xc6a4a7935bd1e995L;
        int r = 47;

        long h = seed ^ (buf.remaining() * m);

        long k;
        while (buf.remaining() >= 8) {
            k = buf.getLong();

            k *= m;
            k ^= k >>> r;
            k *= m;

            h ^= k;
            h *= m;
        }

        if (buf.remaining() > 0) {
            ByteBuffer finish = ByteBuffer.allocate(8).order(
                    ByteOrder.LITTLE_ENDIAN);
            // for big-endian version, do this first:
            // finish.position(8-buf.remaining());
            finish.put(buf).rewind();
            h ^= finish.getLong();
            h *= m;
        }

        h ^= h >>> r;
        h *= m;
        h ^= h >>> r;

        buf.order(byteOrder);
        return h;
    }

    /**
     * Get the md5 of the given key.
     * 计算MD5值
     */
    public byte[] computeMd5(String k) {
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 not supported", e);
        }
        md5.reset();
        byte[] keyBytes = null;
        try {
            keyBytes = k.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unknown string :" + k, e);
        }

        md5.update(keyBytes);
        return md5.digest();
    }

    public static void main(String[] args) {
//        Random ran = new Random();
//        ConsistencyHash hash = new ConsistencyHash();


//        hash.init();
//        hash.printMap();
//        //循环50次，是为了取50个数来测试效果，当然也可以用其他任何的数据来测试
//        for (int i = 0; i < 150; i++) {
////            System.out.println(hash.getShardInfo(hash.hash(hash.computeMd5(String.valueOf(i)), ran.nextInt(hash.VIRTUAL_NUM))));
//            int ii = ran.nextInt(4);
//            System.out.println(ii);
//            System.out.println(hash.getShardInfo(hash.hash(hash.computeMd5("1"),ii)));
//        }
        ConsistencyHash consistencyHash = new ConsistencyHash();
        String u1 = "http://localhost:28080";
        String u2 = "http://localhost:28081";
        System.out.println(consistencyHash.hash(u1));
        System.out.println(consistencyHash.hash(u2));
        System.out.println(u1.getBytes().equals(u2.getBytes()));
    }

}



